using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using CodeSnippet.Config;

namespace CodeSnippet.Formats.Base
{
    /// <summary>
    ///	Provides a base implementation for all code formatters.
    /// </summary>
    /// <remarks>
    /// <para>
    /// To display the formatted code on your web site, the web page must 
    /// refer to a stylesheet that defines the formatting for the different 
    /// CSS classes generated by Items:
    /// .csharpcode, pre, .rem, .kwrd, .str, .op, .preproc, .alt, .lnum.
    /// </para>
    /// <para>
    /// Multi-line comments in your source code (like /* ... */), the "line numbers" or
    /// "alternateLines line background" options will generate code that is not strictly
    /// HTML 4.01 compliant. The code will still look good with IE5+ or Mozilla 0.8+.
    /// </para>
    /// </remarks>
    public abstract class SourceFormat
    {
        #region Fields
        /// <summary>
        /// Regular expression used to capture language tokens.
        /// </summary>
        private Regex _codeRegex;
        #endregion Fields

        #region Properties
        /// <summary>
        /// Regular expression used to capture language tokens.
        /// </summary>
        protected Regex CodeRegex
        {
            get { return _codeRegex ?? (_codeRegex = ConstructCodeRegex()); }
            set { _codeRegex = value; }
        }
        /// <summary>
        /// Gets or sets the source format configuration.
        /// </summary>
        /// <value>The source format configuration.</value>
        public IEditorConfig Editor { get; set; }
        /// <summary>
        /// Gets or sets the style.
        /// </summary>
        /// <value>The style.</value>
        public IStyleConfig Style { get; set; }
        #endregion Properties

        #region CSS
        /// <summary>
        /// Gets the CSS stylesheet as a stream.
        /// </summary>
        /// <returns>A text <see cref="Stream"/> of the CSS definitions.</returns>
        private static Stream CssStream
        {
            get { return Assembly.GetExecutingAssembly().GetManifestResourceStream("CodeSnippet.Resources.csharp.css"); }
        }
        /// <summary>
        /// Gets the CSS stylesheet as a string.
        /// </summary>
        /// <returns>A string containing the CSS definitions.</returns>
        public static string CssString
        {
            // TODO: Change logic to construct style sheet from Style property contents instead of embedded resource.
            get { return new StreamReader(CssStream).ReadToEnd(); }
        }
        /// <summary>
        /// Embeds the styles by replacing <c>class</c> attributes by <c>style</c> attributes.
        /// </summary>
        /// <param name="source">A string containing the HTML formatted code.</param>
        /// <param name="styleConfig">Reference to the style configuration parameters.</param>
        /// <returns>Updated <c>source</c> with matching styles.</returns>
        public static string EmbedStyles(string source, IStyleConfig styleConfig)
        {
            // Prepare the format strings used to process the styles.
            const string classFmt = "class=\"{0}\"";
            const string styleFmt = "style=\"{0}\"";
            // Traverse through each style used for the generated script and replace the styles.
            foreach (DictionaryEntry entry in styleConfig.StyleMap)
            {
                // We need to processe the source multiple time for each supported style and accumulate the changes.
                source = source.Replace(string.Format(CultureInfo.CurrentCulture, classFmt, entry.Key),
                                        string.Format(CultureInfo.CurrentCulture, styleFmt, entry.Value));
            }
            // Return the updated source.
            return source;
        }
        #endregion CSS

        #region Initialization
        /// <summary>
        /// Initializes a new instance of the <see cref="SourceFormat"/> class.
        /// </summary>
        protected SourceFormat()
        {
            Editor = new EditorConfig();
            Style = new StyleConfig();
        }
        /// <summary>
        /// Constructs the code regular expression.
        /// </summary>
        /// <remarks>
        /// Override to construct the regular expression applicable to your <see cref="SourceFormat"/> extension.
        /// </remarks>
        /// <returns>Initialized code regular expression.</returns>
        protected abstract Regex ConstructCodeRegex();
        #endregion Initialization

        #region Formatting
        /// <overloads>Transform source code to HTML 4.01.</overloads>
        /// 
        /// <summary>
        /// Transforms a source code stream to HTML 4.01.
        /// </summary>
        /// <param name="source">Source code stream.</param>
        /// <returns>A string containing the HTML formatted code.</returns>
        public string FormatCode(Stream source)
        {
            var reader = new StreamReader(source);
            var s = reader.ReadToEnd();
            reader.Close();
            return FormatCode(s, Editor, Style, false);
        }
        /// <summary>
        /// Transforms a source code string to HTML 4.01.
        /// </summary>
        /// <param name="source">Source code stream.</param>
        /// <returns>A string containing the HTML formatted code.</returns>
        public string FormatCode(string source)
        {
            return FormatCode(source, Editor, Style, false);
        }
        /// <summary>
        /// Allows formatting a part of the code in a different language,
        /// for example a JavaScript block inside an HTML file.
        /// </summary>
        /// <param name="source">Source code stream.</param>
        /// <returns>A string containing the HTML formatted code.</returns>
        public string FormatSubCode(string source)
        {
            return FormatCode(source, Editor, Style, true);
        }
        /// <summary>
        /// Does the formatting job.
        /// </summary>
        /// <param name="source">Source code stream.</param>
        /// <param name="editorConfig">Reference to the configuration parameters.</param>
        /// <param name="styleConfig">Reference to the style coniguration parameters.</param>
        /// <param name="subCode">if set to <c>true</c> indicates the code provided is part of a larger code set.</param>
        /// <returns>
        /// A string containing the HTML formatted code.
        /// </returns>
        private string FormatCode(string source, IEditorConfig editorConfig, IStyleConfig styleConfig, bool subCode)
        {
            // Ensure we have a valid source to process.
            if (string.IsNullOrEmpty(source))
            {
                return string.Empty;
            }

            // Replace special characters.
            var sb = new StringBuilder(source);

            if (!subCode)
            {
                sb.Replace("&", "&amp;");
                sb.Replace("<", "&lt;");
                sb.Replace(">", "&gt;");
                sb.Replace("\t", string.Empty.PadRight(editorConfig.TabSpaces));
            }

            // Color the code.
            source = CodeRegex.Replace(sb.ToString(), new MatchEvaluator(MatchEval));

            sb = new StringBuilder();

            if (!subCode)
            {
                var cls = (styleConfig.UseContainer ? " class=\"csharpcode-wrapper\"" : string.Empty);
                sb.AppendFormat("<div id=\"codeSnippetWrapper\"{0}>", cls);
            }

            if (styleConfig.LineNumbers || styleConfig.AlternateLines) //we have to process the code line by line
            {
                if (!subCode)
                {
                    sb.AppendLine("<div id=\"codeSnippet\" class=\"csharpcode\">");
                }
                var reader = new StringReader(source);
                var i = 0;
                var spaces = new String(' ', editorConfig.TabSpaces);
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    i++;
                    if (styleConfig.AlternateLines && ((i % 2) == 1))
                    {
                        sb.Append("<pre class=\"alt\">");
                    }
                    else
                    {
                        // CSS class alteven does not exist but it is used as a token for the EmbedStyles logic.
                        sb.Append("<pre class=\"alteven\">");
                    }
                    if (styleConfig.LineNumbers)
                    {
                        var order = (int) Math.Log10(i);
                        sb.AppendFormat("<span id=\"lnum{0}\" class=\"lnum\">{1}:</span> ", i, spaces.Substring(0, 3 - order) + i);
                    }
                    if (line.Length == 0)
                    {
                        sb.Append("&nbsp;");
                    }
                    else
                    {
                        sb.Append(line);
                    }
                    // Notice the use of <!--CRLF--> to simulate a carriage return / line feed combination. This is used when
                    // reloading generated code snippets to replace with an actual carriage return / line feed.
                    sb.Append("</pre><!--CRLF-->");
                }
                reader.Close();
                if (!subCode)
                {
                    sb.Append("</div>");
                }
            }
            else
            {
                // Have to use a <pre> because IE below ver 6 does not understand 
                // the "white-space: pre" CSS value.
                if (!subCode)
                {
                    sb.Append("<pre id=\"codeSnippet\" class=\"csharpcode\">");
                }
                sb.Append(source);
                if (!subCode)
                {
                    sb.Append("</pre>");
                }

                // The following fix must be applied in order to correctly render the contents when posted via RSS feeds.
                // If a <br /> is not added to the end of each line then the code snippet will look all mangled.
                var reader = new StringReader(sb.ToString());
                sb = new StringBuilder();
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    sb.AppendFormat("{0}<br />", line);
                }
                reader.Close();
            }

            if (!subCode)
            {
                sb.Append("</div>");
            }

            return ((styleConfig.EmbedStyles && !subCode) ? EmbedStyles(sb.ToString(), styleConfig) : sb.ToString());
        }
        #endregion Formatting

        #region Overrideables
        /// <summary>
        /// Called to evaluate the HTML fragment corresponding to each 
        /// matching token in the code.
        /// </summary>
        /// <param name="match">The <see cref="Match"/> resulting from a 
        /// single regular expression match.</param>
        /// <returns>A string containing the HTML code fragment.</returns>
        protected abstract string MatchEval(Match match);
        /// <summary>
        /// Used to help determine the likelihood that the given <c>source</c>
        /// matches a the format.
        /// </summary>
        /// <param name="source">Content to use for keyword matching.</param>
        /// <returns>Number of matches found in the given source.</returns>
        public virtual int MatchKeywordCount(string source)
        {
            return 0;
        }
        #endregion Overrideables
    }
}